use std::cell::{RefCell};
use std::collections::HashMap;
use std::rc::Rc;

use crate::class_path::class_path::ClassPath;
use crate::native::java_lang_Class::{java_lang_Class_desiredAssertionStatus0, java_lang_Class_getPrimitiveClass, java_lang_Class_registerNatives};
use crate::native::native_method_register::NativeMethodRegister;
use crate::class::vm_class::VMClass;
use crate::native::java_lang_Object::java_lang_Object_registerNatives;
use crate::native::jdk_internal_misc_VM::jdk_internal_misc_initialize;
use crate::vm_class_loader::{BootstrapClassLoader, ClassLoader};
use crate::vm_thread::{VMFrame, VMThread};

pub struct VM {
    pub main_vm_class: Rc<VMClass>,
    pub jdk_lib_path: String,
    pub class_loader: RefCell<Box<dyn ClassLoader>>,
}

pub struct VMContext {}

pub struct VMParams {
    pub jdk_lib_path: String,
    pub class_path: String,
    pub main_class: String,
}

impl VM {
    pub fn start(params: &VMParams) -> i32 {
        VM::init_native_method();

        let mut vm = VM::create_vm(params).unwrap();
        let find_main_method_result = vm.main_vm_class.find_main_method();
        if find_main_method_result.is_err() {
            println!("err:{}", find_main_method_result.err().unwrap());
            return -1;
        }
        let method = find_main_method_result.unwrap();
        let new_thread_result = VMThread::new_thread(vm.clone());

        if new_thread_result.is_err() {
            println!("err:{}", new_thread_result.err().unwrap());
            return -1;
        }
        let mut vm_thread = new_thread_result.unwrap();
        let get_code_attr_result = method.get_code_attr();
        if get_code_attr_result.is_err() {
            println!("{},{},{}", file!(), line!(), get_code_attr_result.err().unwrap());
            return -1;
        }

        let code_attr = get_code_attr_result.unwrap();
        vm_thread.stack.borrow_mut().push(Rc::new(RefCell::new(VMFrame {
            pc: 0,
            code: code_attr.code.clone(),
            method,
            current_vm_class: vm.clone().main_vm_class.clone(),
            operand_stack: RefCell::new(Vec::with_capacity(code_attr.max_stack as usize)),
            local_vars: RefCell::new(HashMap::with_capacity(code_attr.max_locals as usize)),
        })));
        vm_thread.start();
        return 0;
    }

    fn create_vm(params: &VMParams) -> Result<Rc<VM>, String> {
        let class_loader_result = BootstrapClassLoader::new(&params.jdk_lib_path, &params.class_path);
        if class_loader_result.is_err() {
            return Err(class_loader_result.err().unwrap());
        }
        let mut class_loader = class_loader_result.unwrap();

        let load_class_result = class_loader.load_class(&params.main_class);
        if load_class_result.is_err() {
            return Err(load_class_result.err().unwrap());
        }

        return Ok(Rc::new(VM {
            main_vm_class: load_class_result.unwrap(),
            jdk_lib_path: params.jdk_lib_path.clone(),
            class_loader: RefCell::new(Box::new(class_loader)),
        }));
    }

    fn init_native_method() {
        NativeMethodRegister::register(&String::from("java/lang/Class"),
                                       &String::from("getPrimitiveClass"),
                                       &String::from("(Ljava/lang/String;)Ljava/lang/Class;"),
                                       java_lang_Class_getPrimitiveClass);

        NativeMethodRegister::register(&String::from("java/lang/Class"),
                                       &String::from("registerNatives"),
                                       &String::from("()V"),
                                       java_lang_Class_registerNatives);

        NativeMethodRegister::register(&String::from("java/lang/Class"),
                                       &String::from("desiredAssertionStatus0"),
                                       &String::from("(Ljava/lang/Class;)Z"),
                                       java_lang_Class_desiredAssertionStatus0);

        NativeMethodRegister::register(&String::from("java/lang/Object"),
                                       &String::from("registerNatives"),
                                       &String::from("()V"),
                                       java_lang_Object_registerNatives);

        NativeMethodRegister::register(&String::from("jdk/internal/misc/VM"),
                                       &String::from("initialize"),
                                       &String::from("()V"),
                                       jdk_internal_misc_initialize);
    }
}